package Lexer;

import java.util.*;
import AuxComp.*;

public class Lexer {

	public Lexer(char[] input, CompilationErrorList error) {
		this.input = input;
		// add an end-of-file label to make it easy to do the lexer
		input[input.length - 1] = '\0';
		// number of the current line
		lineNumber = 1;
		tokenPos = 0;
		indexStartLine = 0;
		lastTokenPos = 0;
		beforeLastTokenPos = 0;
		// firstColumn = new Stack<Integer>();
		columnFirstNonBlanck = -1;
		this.error = error;
	}

	public void skipBraces() {
		// skip any of the symbols [ ] { } ( )
		if (token == Symbol.CURLYLEFTBRACE || token == Symbol.CURLYRIGHTBRACE
				|| token == Symbol.LEFTSQBRACKET
				|| token == Symbol.RIGHTSQBRACKET)
			nextToken();
		if (token == Symbol.EOF)
			error.addError("Unexpected EOF");
	}

	public void skipPunctuation() {
		// skip any punctuation symbols
		while (token != Symbol.EOF
				&& (token == Symbol.COLON || token == Symbol.COMMA || token == Symbol.SEMICOLON))
			nextToken();
		if (token == Symbol.EOF)
			error.addError("Unexpected EOF");
	}

	public void skipTo(Symbol[] arraySymbol) {
		// skip till one of the characters of arraySymbol appears in the input
		while (token != Symbol.EOF) {
			int i = 0;
			while (i < arraySymbol.length)
				if (token == arraySymbol[i])
					return;
				else
					i++;
			nextToken();
		}
		if (token == Symbol.EOF)
			error.addError("Unexpected EOF");
	}

	// contains the keywords
	static private Hashtable<String, Symbol> keywordsTable;

	// this code will be executed only once for each program execution
	static {
		keywordsTable = new Hashtable<String, Symbol>();

		keywordsTable.put("se", Symbol.SE);
		keywordsTable.put("senao", Symbol.SENAO);
		keywordsTable.put("senão", Symbol.SENAO);

		keywordsTable.put("repita", Symbol.REPITA);
		keywordsTable.put("enquanto", Symbol.ENQUANTO);
		keywordsTable.put("faca", Symbol.FACA);
		keywordsTable.put("faça", Symbol.FACA);

		keywordsTable.put("para", Symbol.PARA);
		keywordsTable.put("ate", Symbol.ATE);
		keywordsTable.put("até", Symbol.ATE);

		keywordsTable.put("mova", Symbol.MOVA);
		keywordsTable.put("vire", Symbol.VIRE);
		keywordsTable.put("abaixa", Symbol.ABAIXA);
		keywordsTable.put("levanta", Symbol.LEVANTA);
		keywordsTable.put("apaga", Symbol.APAGA);
		keywordsTable.put("e", Symbol.E);
		keywordsTable.put("ou", Symbol.OU);
		keywordsTable.put("nao", Symbol.NAO);
		keywordsTable.put("não", Symbol.NAO);

		keywordsTable.put("verdadeiro", Symbol.VERDADEIRO);
		keywordsTable.put("falso", Symbol.FALSO);

	}

	public int getColumnNumber() {
		return indexStartLine - tokenPos + 1;
	}


	public void nextToken() {
		char ch;

		while ((ch = input[tokenPos]) == ' ' || ch == '\r' || ch == '\t'
				|| ch == '\n') {
			if (ch == ' ' || ch == '\t')
				columnFirstNonBlanck++;
			// count the number of lines
			if (ch == '\n') {
				lineNumber++;
				if (ch == '\r') {
					tokenPos++;
				}
				indexStartLine = tokenPos;
			}

			tokenPos++;
		}
		columnFirstNonBlanck = tokenPos - indexStartLine;
		/*
		if ( firstColumn.lastElement() > columnFirstNonBlanck ) {
			token = Symbol.EOS;
			return;
		}
		*/
		if (ch == '\0')
			token = Symbol.EOF;
		else if (input[tokenPos] == '/' && input[tokenPos + 1] == '/') {
			// comment found
			while (input[tokenPos] != '\0' && input[tokenPos] != '\n')
				tokenPos++;
			nextToken();
		} else {
			if (Character.isLetter(ch)) {
				// get an identifier or keyword
				StringBuffer ident = new StringBuffer();
				while (Character.isLetter(input[tokenPos])
						|| Character.isDigit(input[tokenPos])) {
					char ch1 = input[tokenPos];
					switch (ch1) {
					case 'Ç':
						ch = 'ç';
						break;
					case 'Ã':
						ch = 'ã';
						break;
					case 'É':
						ch = 'é';
						break;
					}

					ident.append(Character.toLowerCase(ch1));
					tokenPos++;
				}
				stringValue = ident.toString();
				// if identStr is in the list of keywords, it is a keyword !
				Symbol value = keywordsTable.get(stringValue);
				if (value == null)
					token = Symbol.IDENT;
				else
					token = value;
			} else if (Character.isDigit(ch)) {
				// get a number
				StringBuffer number = new StringBuffer();
				while (Character.isDigit(input[tokenPos])) {
					number.append(input[tokenPos]);
					tokenPos++;
				}
				token = Symbol.NUMBER;
				try {
					numberValue = Integer.valueOf(number.toString()).intValue();
				} catch (NumberFormatException e) {
					error.addError("Number out of limits");
				}
				if (numberValue >= MaxValueInteger)
					error.addError("Number out of limits");
			} else {
				tokenPos++;
				switch (ch) {
				case '@':
					token = Symbol.AT;
					break;
				case '+':
					token = Symbol.PLUS;
					break;
				case '-':
					token = Symbol.MINUS;
					break;
				case '*':
					token = Symbol.MULT;
					break;
				case '/':
					token = Symbol.DIV;
					break;
				case '%':
					token = Symbol.REMAINDER;
					break;
				case '<':
					if (input[tokenPos] == '=') {
						tokenPos++;
						token = Symbol.LE;
					} else
						token = Symbol.LT;
					break;
				case '>':
					if (input[tokenPos] == '=') {
						tokenPos++;
						token = Symbol.GE;
					} else
						token = Symbol.GT;
					break;
				case '!':
					if (input[tokenPos] == '=') {
						tokenPos++;
						token = Symbol.NEQ;
					} else
						error.addError("Invalid Character: '!'");
					break;
				case '=':
					if (input[tokenPos] == '=') {
						tokenPos++;
						token = Symbol.EQ;
					} else
						token = Symbol.ASSIGN;
					break;
				case '(':
					token = Symbol.LEFTPAR;
					break;
				case ')':
					token = Symbol.RIGHTPAR;
					break;
				case ',':
					token = Symbol.COMMA;
					break;
				case ';':
					token = Symbol.SEMICOLON;
					break;
				case ':':
					token = Symbol.COLON;
					break;
				case '\'':
					token = Symbol.LITERAL_CHARACTER;
					charValue = input[tokenPos];
					tokenPos++;
					if (input[tokenPos] != '\'')
						error.addError("Illegal literal character"
								+ input[tokenPos - 1]);
					tokenPos++;
					break;
				// the next four symbols are not used by the language
				// but are returned to help the error treatment
				case '{':
					token = Symbol.CURLYLEFTBRACE;
					break;
				case '}':
					token = Symbol.CURLYRIGHTBRACE;
					break;
				case '[':
					token = Symbol.LEFTSQBRACKET;
					break;
				case ']':
					token = Symbol.RIGHTSQBRACKET;
					break;
				case '"':
					token = Symbol.LITERAL_STRING;
					StringBuffer sb = new StringBuffer();
					while (true) {
						if (input[tokenPos] == '"'
								&& input[tokenPos - 1] != '\\')
							break;
						sb.append(input[tokenPos]);
						tokenPos++;
					}
					literalString = sb.toString();
				default:
					error.addError("Invalid Character: '" + ch + "'");
				}
			}
		}
		beforeLastTokenPos = lastTokenPos;
		lastTokenPos = tokenPos - 1;
	}

	// return the line number of the last token got with getToken()
	public int getLineNumber() {
		return lineNumber;
	}

	public int getLineNumberBeforeLastToken() {
		return getLineNumber(beforeLastTokenPos);
	}

	private int getLineNumber(int index) {
		// return the line number in which the character input[index] is
		int i, n, size;
		n = 1;
		i = 0;
		size = input.length;
		while (i < size && i < index) {
			if (input[i] == '\n')
				n++;
			i++;
		}
		return n;
	}

	public String getCurrentLine() {
		return getLine(lastTokenPos);
	}

	public String getLineBeforeLastToken() {
		return getLine(beforeLastTokenPos);
	}

	private String getLine(int index) {
		// get the line that contains input[index]. Assume input[index] is at a
		// token, not
		// a white space or newline

		int i = index;
		if (i == 0)
			i = 1;
		else if (i >= input.length)
			i = input.length;

		StringBuffer line = new StringBuffer();
		// go to the beginning of the line
		while (i >= 1 && input[i] != '\n')
			i--;
		if (input[i] == '\n')
			i++;
		// go to the end of the line putting it in variable line
		while (input[i] != '\0' && input[i] != '\n' && input[i] != '\r') {
			line.append(input[i]);
			i++;
		}
		return line.toString();
	}

	public String getStringValue() {
		return stringValue;
	}

	public int getNumberValue() {
		return numberValue;
	}

	public char getCharValue() {
		return charValue;
	}

	public void setLiteralString(String literalString) {
		this.literalString = literalString;
	}

	public String getLiteralString() {
		return literalString;
	}

	// current token
	public Symbol token;
	private String stringValue;
	private int numberValue;
	private char charValue;
	private String literalString;

	/**
	 * column in which the last line started
	 */

	private int tokenPos;
	// input[lastTokenPos] is the last character of the last token found
	private int lastTokenPos;
	// input[beforeLastTokenPos] is the last character of the token before the
	// last
	// token found
	private int beforeLastTokenPos;
	// program given as input - source code
	private char[] input;

	// number of current line. Starts with 1
	private int lineNumber;

	private CompilationErrorList error;
	private static final int MaxValueInteger = 32768;
	/*
	 * stack of the first non-blanck characters. A column number is only stacked
	 * if it is greater than the previous one. This means a new nested command
	 * has started (as in a se-senão command).
	 */
	// private Stack<Integer> firstColumn;
	/*
	 * the column of the first non-blanck character of this line or of the
	 * previous line (if the lexer has not found yet a non-blanck character in
	 * the current line).
	 */
	private int columnFirstNonBlanck;

	public int getColumnFirstNonBlanck() {
		return columnFirstNonBlanck;
	}

	/*
	 * index of input of the first character of this line.
	 */
	private int indexStartLine;

}
